package pl.koszolko.documentclustering.cluster.quality;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.Logger;

import pl.koszolko.documentclustering.cluster.Cluster;
import pl.koszolko.documentclustering.cluster.document.DocumentForClustering;

/**
 * Oblicza jakosc grupowania na podstawie wskaznika F
 * @author Wojtek
 *
 */
public class FQualityHelper implements QualityMeasurementHelper {
	//wspolczynnik do obliczenie wskanika F
	private static double Beta = 2;
	
	private static final Logger LOGGER = Logger.getLogger(FQualityHelper.class);
	
	@Override
	public double measureQuality(List<Cluster> finalClusters) {
		//suma wszystkich mozliwych par w klastrze
		long tpFp = 0;
		//suma wszystkich para obiektow tej samej klasy w klastrach
		long tp = 0;
		//suma wszystkich para obiektow tej samej klasy
		long fnTn = 0;
		//suma wszystkich blednych par (obiekty tej samej klasy sa w roznych klastrach)
		long fn = 0;
		
		Map<String, Integer> numberOfDocumentTheSameClass = new HashMap<String, Integer>();
		List<Map<String, Integer>> listOfNumberOfDocumentPerClassPerCluster = new ArrayList<Map<String,Integer>>();
		
		for(Cluster cluster : finalClusters) {
			
			int numberOfDocumentInCluster = cluster.getDocuments().size();
			Map<String, Integer> numberOfDocumentTheSameClassPerCluster = new HashMap<String, Integer>();

			tpFp += binomial(numberOfDocumentInCluster, 2);
			
			for(DocumentForClustering doc : cluster.getDocuments()) {
				String documentClass = doc.getDocumentClass();
				
				Integer numberOfDocumentPerClass = numberOfDocumentTheSameClass.get(documentClass);
				if(numberOfDocumentPerClass == null) {
					numberOfDocumentPerClass = 0;
				}
				numberOfDocumentTheSameClass.put(documentClass, numberOfDocumentPerClass+1);
				
				Integer numberOfDocumentPerClassPerCluster = numberOfDocumentTheSameClassPerCluster.get(documentClass);
				if(numberOfDocumentPerClassPerCluster == null) {
					numberOfDocumentPerClassPerCluster = 0;
				}
				numberOfDocumentTheSameClassPerCluster.put(documentClass, numberOfDocumentPerClassPerCluster+1);
			}
			
			listOfNumberOfDocumentPerClassPerCluster.add(numberOfDocumentTheSameClassPerCluster);
		}
		
		Set<String> allDocumentClass = numberOfDocumentTheSameClass.keySet();
		
		for(String documentClass : allDocumentClass) {
			int numberOfDocumentPerClass = numberOfDocumentTheSameClass.get(documentClass);
			fnTn += binomial(numberOfDocumentPerClass, 2);
			LOGGER.debug("Liczba obiektow klasy " + documentClass + " wynosi " + numberOfDocumentPerClass);
		}
		
		int numberOfClusters = finalClusters.size();
		for(String documentClass : allDocumentClass) {
			for(int i=0 ; i<numberOfClusters-1 ; ++i) {
				Integer numberOfDocumentInClusterI = listOfNumberOfDocumentPerClassPerCluster.get(i).get(documentClass);
				for(int j=i+1 ; j<numberOfClusters ; ++j) {
					Integer numberOfDocumentInClusterJ = listOfNumberOfDocumentPerClassPerCluster.get(j).get(documentClass);
					
					if(numberOfDocumentInClusterI!=null && numberOfDocumentInClusterJ!=null) {
						fn += numberOfDocumentInClusterI * numberOfDocumentInClusterJ;
					}
				}
			}
		}
		
		LOGGER.debug("Liczba dokumentow tej samej klasy w kazdym z klastrow");
		int i = 0;
		for(Map<String, Integer> numberOfDocumentPerClassPerCluster : listOfNumberOfDocumentPerClassPerCluster) {
			LOGGER.debug("Klaster - " + i);
			for(String documentClass : numberOfDocumentPerClassPerCluster.keySet()) {
				int numberOfDoc = numberOfDocumentPerClassPerCluster.get(documentClass);
				LOGGER.debug("Klasa - " + documentClass + " liczba elementow - " + numberOfDoc);
				//czy jest wiecej niz dwa dokumenty tej samej klasy w klastrze (zeby mozna bylo wyznaczyc pare)
				if(numberOfDoc>1) {
					tp += binomial(numberOfDoc, 2);
				}
			}
		}
		
		LOGGER.debug("tpFp = " + tpFp);
		LOGGER.debug("tp = " + tp);
		LOGGER.debug("fnTn = " + fnTn);
		LOGGER.debug("fn = " + fn);
		long fp = tpFp - tp;
		
		double P = tp/(double)(tp+fp);
		double R = tp/(double)(tp+fn);
		
		LOGGER.info("P=" + P);
		LOGGER.info("R=" + R);
		
		double F = ((Math.pow(Beta, 2) +1)*P*R)/(double)(Math.pow(Beta, 2)*P+R);
		
		return F*100.0;
	}
	
	/**
	 * Oblicza symbol newtona
	 * @param N
	 * @param K
	 * @return
	 */
    private static long binomial(int N, int K) {
        long[][] binomial = new long[N+1][K+1];

        // base cases
        for (int k = 1; k <= K; k++) binomial[0][k] = 0;
        for (int n = 0; n <= N; n++) binomial[n][0] = 1;

        // bottom-up dynamic programming
        for (int n = 1; n <= N; n++)
            for (int k = 1; k <= K; k++)
                binomial[n][k] = binomial[n-1][k-1] + binomial[n-1][k];

        return binomial[N][K];
    }

}
